package gpeerreview.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;

class KeyPairFactory
{
	private int m_state;
	private byte[] m_entropy;
	private Label m_status;
	private Label m_progress;
	private byte[] m_digest1;
	private byte[] m_digest2;
	private byte[] m_digest3;
	private BigInt.PrimeMaker m_pm1;
	private BigInt.PrimeMaker m_pm2;
	private BigInt m_n;
	private BigInt m_phi;
	private BigInt m_public;
	private BigInt m_private;
	private final float[] m_spread = { 0.1f, 0.3f, 0.5f, 0.7f, 0.8f, 0.9f, 1.0f, 1.0f };
	private float m_fudgeSmoother;

	public KeyPairFactory(byte[] entropy, Label status, Label progress)
	{
		m_entropy = entropy;
		m_status = status;
		m_progress = progress;
		setState(0, "Hashing entropy");
	}

	private void setState(int newState, String status)
	{
		m_state = newState;
		m_fudgeSmoother = 1.0f;
		m_status.setText("(" + status + ")");
	}

	private void updateProgress()
	{
		m_fudgeSmoother *= 0.993;
		float prog = 100 * (m_spread[m_state] + (1.0f - m_fudgeSmoother) * (m_spread[m_state + 1] - m_spread[m_state]));
		m_progress.setText("" + prog + "%");
	}

	private byte[] clipArray(byte[] arr, int maxSize)
	{
		if(arr.length <= maxSize)
			return arr;
		byte[] newArr = new byte[maxSize];
		for(int i = 0; i < maxSize; i++)
			newArr[i] = arr[i];
		return newArr;
	}

	public boolean crunch()
	{
		updateProgress();
		if(m_state == 0)
		{
			// Hash the entropy
			int digestSize = 8; //32;
			int entropy_part = m_entropy.length / 3;
			byte[] entropyPart1 = new byte[entropy_part];
			for(int i = 0; i < entropy_part; i++)
				entropyPart1[i] = m_entropy[i];
			sha256 sha = new sha256();
			m_digest1 = clipArray(sha.hash(entropyPart1), digestSize);
			byte[] entropyPart2 = new byte[entropy_part];
			for(int i = 0; i < entropy_part; i++)
				entropyPart2[i] = m_entropy[entropy_part + i];
			sha = new sha256();
			m_digest2 = clipArray(sha.hash(entropyPart2), digestSize);
			byte[] entropyPart3 = new byte[entropy_part];
			for(int i = 0; i < entropy_part; i++)
				entropyPart3[i] = m_entropy[entropy_part + entropy_part + i];
			sha = new sha256();
			m_digest3 = clipArray(sha.hash(entropyPart3), digestSize);

			// Get ready for the next state
			m_pm1 = BigInt.ZERO.new PrimeMaker(m_digest1, 500/*certainty*/);
			m_pm2 = BigInt.ZERO.new PrimeMaker(m_digest2, 500/*certainty*/);
			setState(1, "Seeking first prime");
			return false;
		}
		else if(m_state == 1)
		{
			// Find first prime
			if(m_pm1.crunch())
				setState(2, "Seeking second prime");
			return false;
		}
		else if(m_state == 2)
		{
			// Find second prime
			if(m_pm2.crunch())
				setState(3, "Computing intermediate values");
			return false;
		}
		else if(m_state == 3)
		{
			BigInt prime1 = m_pm1.getResults();
			BigInt prime2 = m_pm2.getResults();
			m_n = prime1.multiply(prime2);
			BigInt pm1 = prime1.subtract(BigInt.ONE);
			BigInt qm1 = prime2.subtract(BigInt.ONE);
			m_phi = pm1.multiply(qm1);
			setState(4, "Computing public key");
			return false;
		}
		else if(m_state == 4)
		{
			for(int i = 0; i < 5; i++)
			{
				BigInt.shuffleEntropy(m_digest3);
				m_public = new BigInt(m_digest3);
				if(m_phi.gcd(m_public).equals(BigInt.ONE))
				{
					setState(5, "Computing private key");
					return false;
				}
			}
			return false;
		}
		else if(m_state == 5)
		{
			m_private = m_public.modInverse(m_phi);
			m_progress.setText("100%");
			setState(6, "Done");
			return true;
		}
		else
		{
			Window.alert("internal error--unexpected state");
			return true;
		}
	}

	public KeyPair getResults()
	{
		KeyPair kp = new KeyPair();
		kp.m_public = m_public;
		kp.m_private = m_private;
		kp.m_n = m_n;
		return kp;
	}
}
